using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Ink;
using System.Collections;
using System.IO;
using System.Drawing;

namespace edu.stanford.hci.ideas
{
    class ClusterManager
    {
        // each cluster manager has a unique guid: allows multiple cluster mgrs
        private Guid ClusterGuid;

        private int id = 0; // next id to allocate
        private InkCollector inkCollector;
        private Hashtable clusterTable = new Hashtable();

        public ClusterManager(InkCollector inkCollector)
        {
            this.inkCollector = inkCollector;
            this.ClusterGuid = Guid.NewGuid();
        }

        private void AddToCluster(Stroke newStroke, int clusterId)
        {
            Strokes cluster = (Strokes)clusterTable[clusterId];
            cluster.Add(newStroke);
            SetStrokeClusterId(newStroke, clusterId);
        }

        public Strokes GetClusterForStroke(Stroke s)
        {
            int clusterId = GetStrokeClusterId(s);
            if (clusterId == -1)
            {
                // TODO: this should never happen: every stroke should have a cluster, but somehow they're getting in...
                return null;
                //throw new Exception("no cluster found for this stroke!");
            }
            Strokes cluster = (Strokes)clusterTable[clusterId];
            if (cluster == null)
            {
                // TODO: this should never happen: every stroke should have a cluster, but somehow they're getting in...
                return null;
                // this should never happen either: every cluster should exist
                //throw new Exception("cluster id does not exist!");
            }
            return cluster;
        }

        public void RemoveFromCluster(Stroke newStroke)
        {
            int clusterId = GetStrokeClusterId(newStroke);
            if (clusterId == -1)
            {
                // this should never happen: every stroke should have a cluster
                throw new Exception("no cluster found for this stroke! (-1)");
            }
            Strokes cluster = (Strokes)clusterTable[clusterId];
            if (cluster == null)
            {
                // this should never happen either: every cluster should exist!
                throw new Exception("no cluster found for this stroke! (null)");
            }

            // remove the stroke from the cluster
            cluster.Remove(newStroke);
            ClearStrokeClusterId(newStroke);

            // if empty cluster, remove the cluster from the table
            if (cluster.Count == 0)
            {
                clusterTable.Remove(clusterId);
            }
        }

        private int CreateNewCluster()
        {
            Strokes newCluster = inkCollector.Ink.CreateStrokes();
            clusterTable.Add(id, newCluster);
            return id++;
        }

        private void ClearStrokeClusterId(Stroke s)
        {
            s.ExtendedProperties.Remove(ClusterGuid);
        }

        private int GetStrokeClusterId(Stroke s)
        {
            if (!s.ExtendedProperties.DoesPropertyExist(ClusterGuid))
            {
                // this should never happen: every stroke should have a cluster
                // TODO: this should never happen: every stroke should have a cluster, but somehow they're getting in...
                return -1;
                //throw new Exception("no cluster found for this stroke!");
            }
            ExtendedProperty ep = s.ExtendedProperties[ClusterGuid];
            Byte[] bt = (Byte[])ep.Data;
            BinaryReader binread = new BinaryReader(new MemoryStream(bt));
            int id = binread.ReadInt32();
            return id;
        }

        private void SetStrokeClusterId(Stroke s, int id)
        {
            try
            {
                MemoryStream memstr = new MemoryStream();
                BinaryWriter binwrite = new BinaryWriter(memstr);

                binwrite.Write(id);
                Byte[] bt = memstr.ToArray();

                memstr.Close();
                binwrite.Close();

                s.ExtendedProperties.Add(ClusterGuid, bt);
            }
            catch (Exception ex)
            {
                string msg = ex.Message;
            }
        }

        private double[] CalculateSpaceDiffs(Stroke newStroke)
        {
            Point newStrokePoint = GetBoundingBoxCentroid(newStroke.GetBoundingBox());
            double[] results = new double[inkCollector.Ink.Strokes.Count];

            int i = 0;
            foreach (Stroke theStroke in inkCollector.Ink.Strokes)
            {
                Point sPoint = GetBoundingBoxCentroid(theStroke);
                results[i] = Math.Sqrt(Math.Pow(sPoint.X - newStrokePoint.X, 2) + Math.Pow(sPoint.Y - newStrokePoint.Y, 2));
                i++;
            }

            return results;
        }

        private Point GetBoundingBoxCentroid(Stroke s)
        {
            return GetBoundingBoxCentroid(s.GetBoundingBox());
        }

        private Point GetBoundingBoxCentroid(Rectangle box)
        {
            return box.Location + new Size(box.Width / 2, box.Height / 2);
        }

        private long[] CalculateTimeDiffs(Stroke newStroke)
        {
            long newStrokeTimestamp = Timestamp.GetTimestampForStroke(newStroke);
            long[] results = new long[inkCollector.Ink.Strokes.Count];
            //inkCollector.Enabled = false;
            //Console.WriteLine("Counter: " + inkCollector.Ink.Strokes.Count);
            //for (int i = 0; i < 500; i++)
            //{
//
            //}
            int i = 0;
            foreach (Stroke theStroke in inkCollector.Ink.Strokes)
            {
                results[i] = Math.Abs(newStrokeTimestamp - Timestamp.GetTimestampForStroke(theStroke));
                i++;
            }
            //for (int i = 0; i < count; i++)
            //{
            //    Strokes.StrokesEnumerator
             //   Stroke s = inkCollector.Ink.Strokes[i];
                //
            //}

            return results;
        }

        public void ClusterNewStroke(Stroke newStroke)
        {
            ClusterSpaceAndTime(newStroke);
        }

        private void ClusterTime(Stroke newStroke)
        {
            long[] timeResults = CalculateTimeDiffs(newStroke);
            int smallestDiffIndex = -1;
            long smallestDiffValue = long.MaxValue;

            for (int i = 0; i < inkCollector.Ink.Strokes.Count; i++)
            {
                if (timeResults[i] < smallestDiffValue
                    && timeResults[i] < 20000000
                    && inkCollector.Ink.Strokes[i].Id != newStroke.Id)
                {
                    smallestDiffIndex = i;
                    smallestDiffValue = timeResults[i];
                }
            }

            //Console.WriteLine("Index: " + smallestDiffIndex);
            //Console.WriteLine("Value: " + smallestDiffValue);

            if (smallestDiffIndex == -1)
            {
                // this is its own cluster, make a new one
                int newClusterId = CreateNewCluster();
                AddToCluster(newStroke, newClusterId);
                //Console.WriteLine("New cluster: " + newClusterId);
                return;
            }

            // otherwise, pick the cluster that the winning stroke belonged to
            int clusterId = GetStrokeClusterId(inkCollector.Ink.Strokes[smallestDiffIndex]);
            AddToCluster(newStroke, clusterId);
            //Console.WriteLine("Old cluster: " + clusterId);
            return;
        }

        private void ClusterSpace(Stroke newStroke)
        {
            double[] spaceResults = CalculateSpaceDiffs(newStroke);
            int smallestDiffIndex = -1;
            double smallestDiffValue = double.MaxValue;

            for (int i = 0; i < inkCollector.Ink.Strokes.Count; i++)
            {
                if (spaceResults[i] < smallestDiffValue
                    && spaceResults[i] < 3000
                    && inkCollector.Ink.Strokes[i].Id != newStroke.Id)
                {
                    smallestDiffIndex = i;
                    smallestDiffValue = spaceResults[i];
                }
            }

//            Console.WriteLine("Index: " + smallestDiffIndex);
            //Console.WriteLine("Value: " + smallestDiffValue);

            if (smallestDiffIndex == -1)
            {
                // this is its own cluster, make a new one
                int newClusterId = CreateNewCluster();
                AddToCluster(newStroke, newClusterId);
              //  Console.WriteLine("New cluster: " + newClusterId);
                return;
            }

            // otherwise, pick the cluster that the winning stroke belonged to
            int clusterId = GetStrokeClusterId(inkCollector.Ink.Strokes[smallestDiffIndex]);
            AddToCluster(newStroke, clusterId);
            //Console.WriteLine("Old cluster: " + clusterId);
            return;
        }

        private void ClusterSpaceAndTime(Stroke newStroke)
        {
            DateTime time1 = DateTime.Now;
            double[] spaceResults = CalculateSpaceDiffs(newStroke);
            DateTime time2 = DateTime.Now;
            
            //double[] spaceResults = new double[inkCollector.Ink.Strokes.Count];
            long[] timeResults = CalculateTimeDiffs(newStroke);
            DateTime time3 = DateTime.Now;


            //long[] timeResults = new long[inkCollector.Ink.Strokes.Count];
            int smallestDiffIndex = -1;
            double smallestDiffValue = double.MaxValue;

            //Console.WriteLine("Num strokes: " + inkCollector.Ink.Strokes.Count);
            //for (int i = 0; i < inkCollector.Ink.Strokes.Count; i++)
            int i = 0;
            foreach (Stroke theStroke in inkCollector.Ink.Strokes)
            {
                if (((spaceResults[i] < smallestDiffValue
                    && spaceResults[i] < 3000) ||
                    (timeResults[i] < smallestDiffValue
                    && timeResults[i] < 10000000))
                    && theStroke.Id != newStroke.Id)
                {
                    smallestDiffIndex = i;
                    smallestDiffValue = (double)spaceResults[i] / (double)2000 + (double)timeResults[i] / (double)20000000;
                }
                i++;
            }

            //Console.WriteLine("Index: " + smallestDiffIndex);
            //Console.WriteLine("Value: " + smallestDiffValue);

            DateTime time4 = DateTime.Now;

            TimeSpan span1 = time2 - time1;
            TimeSpan span2 = time3 - time2;
            TimeSpan span3 = time4 - time3;

            Console.WriteLine("Spans: " + span1.TotalSeconds + ", " + span2.TotalSeconds + ", " + span3.TotalSeconds);
            if (smallestDiffIndex == -1)
            {
                // this is its own cluster, make a new one
                int newClusterId = CreateNewCluster();
                AddToCluster(newStroke, newClusterId);
                return;
            }

            // otherwise, pick the cluster that the winning stroke belonged to
            int clusterId = GetStrokeClusterId(inkCollector.Ink.Strokes[smallestDiffIndex]);
            AddToCluster(newStroke, clusterId);

            return;
        }

        public void ReclusterStrokes(Strokes strokes)
        {
            Rectangle r = strokes.GetBoundingBox();
            Point p = GetBoundingBoxCentroid(r);

            // make a new cluster for these strokes
            int newClusterId = CreateNewCluster();
            foreach (Stroke stroke in strokes)
            {
                RemoveFromCluster(stroke);
                AddToCluster(stroke, newClusterId);
            }
            Strokes newCluster = (Strokes)clusterTable[newClusterId];
            
            // if we're in another cluster, add these strokes to that cluster
            foreach (Strokes cluster in GetClusters())
            {
                if (cluster != newCluster && cluster.GetBoundingBox().Contains(p))
                {
                    Console.WriteLine("Inside another rect!!!");
                    int clusterId = GetStrokeClusterId(cluster[0]);
                    foreach (Stroke stroke in newCluster)
                    {
                        RemoveFromCluster(stroke);
                        AddToCluster(stroke, clusterId);
                    }
                    return;
                }
            }
        }

        public void CreateNewClusterFromStrokes(Strokes strokes)
        {
            int newClusterId = CreateNewCluster();
            foreach (Stroke stroke in strokes)
            {
                if (GetStrokeClusterId(stroke) != -1)
                {
                    RemoveFromCluster(stroke);
                }
                AddToCluster(stroke, newClusterId);
            }
        }

        public ICollection GetClusters()
        {
            return clusterTable.Values;
        }
    }
}
